#region Copyright (c) 2007 DotNetSlackers.com
/*
[===========================================================================]
[   Copyright (c) 2007, DotNetSlackers.com                                  ]
[   All rights reserved.                                                    ]
[                                                                           ]
[   Redistribution and use in source and binary forms, with or without      ]
[   modification, are permitted provided that the following conditions      ]
[   are met:                                                                ]
[                                                                           ]
[   * Redistributions of source code must retain the above copyright        ]
[   notice, this list of conditions and the following disclaimer.           ]
[                                                                           ]
[   * Redistributions in binary form must reproduce the above copyright     ]
[   notice, this list of conditions and the following disclaimer in         ]
[   the documentation and/or other materials provided with the              ]
[   distribution.                                                           ]
[                                                                           ]
[   * Neither the name of DotNetSlackers.com nor the names of its           ]
[   contributors may be used to endorse or promote products derived         ]
[   from this software without specific prior written permission.           ]
[                                                                           ]
[   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS     ]
[   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT       ]
[   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS       ]
[   FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE          ]
[   COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,     ]
[   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES                ]
[   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR      ]
[   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)      ]
[   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,     ]
[   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING   ]
[   IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE      ]
[   POSSIBILITY OF SUCH DAMAGE.                                             ]
[===========================================================================]
*/
#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.ComponentModel;
using System.Drawing.Design;
using System.Security.Permissions;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.Design;
using System.Web.Script.Serialization;


[assembly: WebResource(AjaxDataControls.GridViewDropDownListColumn.ScriptReleaseFile, "text/javascript")]
[assembly: WebResource(AjaxDataControls.GridViewDropDownListColumn.ScriptDebugFile, "text/javascript")]

namespace AjaxDataControls
{
    /// <summary>
    /// Represents a selecting option that is displayed as a dropdown list in a <see cref="GridView"/> control.
    /// </summary>
    /// <seealso cref="GridView"/>
    /// <seealso cref="GridViewBoundColumn"/>
    /// <seealso cref="GridViewButtonColumn"/>
    /// <seealso cref="GridViewCommandColumn"/>
    /// <seealso cref="GridViewHyperLinkColumn"/>
    /// <seealso cref="GridViewImageColumn"/>
    /// <seealso cref="GridViewTemplateColumn"/>
    [AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [ParseChildren(true)]
    [TypeConverter(typeof(ExpandableObjectConverter))]
    public class GridViewDropDownListColumn : GridViewBaseColumn
    {
        internal const string ScriptFileBase = "AjaxDataControls.GridView.Columns.GridViewDropDownListColumn";
        internal const string ScriptReleaseFile = ScriptFileBase + ".js";
        internal const string ScriptDebugFile = ScriptFileBase + ".debug.js";

        object _dataSource;
        string _dataField = string.Empty;
        string _dataTextField = string.Empty;
        string _dataTextFormatString = string.Empty;
        string _dataValueField = string.Empty;
        bool _readOnly;

        /// <summary>
        /// Gets the base name of the script file without the extension.
        /// </summary>
        /// <value>The name of the script file base name.</value>
        /// <remarks>
        /// This method will return the base name without the .debug.js or .js extension of the associated script file of this column.
        /// </remarks>
        protected override string ScriptBaseFileName
        {
            [DebuggerStepThrough()]
            get
            {
                return ScriptFileBase;
            }
        }

        /// <summary>
        /// Gets or sets the object from which the data-bound control retrieves its list of data items.
        /// </summary>
        /// <value>An object that represents the data source from which the data-bound control retrieves its data. The default is null.</value>
        [Category("Data")]
        [DefaultValue(null)]
        [NotifyParentProperty(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual object DataSource
        {
            get { return _dataSource; }
            set
            {
                if (value != null)
                {
                    ValidateDataSource(value);
                }
                _dataSource = value;
            }
        }


        /// <summary>
        /// Gets or sets the name of the member field to display text in a <see cref="GridViewDropDownListColumn"/> object.
        /// </summary>
        /// <remarks>
        /// The <b>DropDownListDataText</b> property is used to bind data into the dropdown list automatically, based on data of the <see cref="GridViewDropDownListColumn.DataSource"/> property.
        /// If the <b>DropDownListDataText</b> property is set to an empty string, no data is rendered.
        /// </remarks>
        /// <value>The name of the member field to display text to bind the DropDownList. The default is an empty string (""), which indicates that this property is not set.</value>
        [Category("Data")]
        [DefaultValue("")]
        [NotifyParentProperty(true)]
        public virtual string DropDownListDataText
        {
            get { return _dataTextField; }
            set
            {
                if (_dataTextField != value)
                    _dataTextField = value;
            }
        }

        /// <summary>
        /// Gets or sets the string that specifies the display format for the display text of the field.
        /// </summary>
        /// <remarks>
        /// Use the <b>DataFormatString</b> property to specify a custom display format for the text displayed in the <see cref="GridViewDropDownListColumn"/> object. If the DataFormatString property is not set, the field's text is displayed without any special formatting.
        /// </remarks>
        /// <value>A formatting string that specifies the display format for the text of the <see cref="GridViewDropDownListColumn"/>. The default is an empty string (""), which indicates that no special formatting is applied to the field text.</value>
        [Category("Data")]
        [DefaultValue("")]
        [NotifyParentProperty(true)]
        public virtual string DropDownListDataTextFormatString
        {
            get { return _dataTextFormatString; }
            set
            {
                if (_dataTextFormatString != value)
                    _dataTextFormatString = value;
            }
        }

        /// <summary>
        /// Gets or sets the name of the member with the value in a <see cref="GridViewDropDownListColumn"/> object.
        /// </summary>
        /// <remarks>
        /// The <b>DropDownListDataValue</b> property is used to bind data into the dropdown list automatically, based on data of the <see cref="GridViewDropDownListColumn.DataSource"/> property.
        /// If the <b>DropDownListDataValue</b> property is set to an empty string, no data is binded.
        /// </remarks>
        /// <value>The name of the member field with the value to bind the DropDownList. The default is an empty string (""), which indicates that this property is not set.</value>
        [Category("Data")]
        [DefaultValue("")]
        [NotifyParentProperty(true)]
        public virtual string DropDownListDataValue
        {
            get { return _dataValueField; }
            set
            {
                if (_dataValueField != value)
                    _dataValueField = value;
            }
        }


        /// <summary>
        /// Gets or sets the name of the data field to bind to the <see cref="GridViewDropDownListColumn"/> object.
        /// </summary>
        /// <remarks>
        /// Use the <b>DataField</b> property to specify the name of the data field to bind to the <see cref="GridViewDropDownListColumn"/> object.
        /// </remarks>
        /// <value>The name of the data field to bind to the DropDownList. The default is an empty string (""), which indicates that this property is not set.</value>
        [Category("Data")]
        [DefaultValue("")]
        [NotifyParentProperty(true)]
        public virtual string DataField
        {
            get { return _dataField; }
            set
            {
                if (_dataField != value)
                    _dataField = value;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the value of the field can be modified in edit mode.
        /// </summary>
        /// <value><c>true</c> to prevent the value of the field from being modified in edit mode; otherwise, <c>false</c>. The default is <c>false</c>.</value>
        [Category("Behavior")]
        [DefaultValue(false)]
        public bool ReadOnly
        {
            get { return _readOnly; }
            set
            {
                if (_readOnly != value)
                    _readOnly = value;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GridViewDropDownListColumn"/> class.
        /// </summary>
        public GridViewDropDownListColumn()
            : base()
        {
            base.AllowDragAndDrop = false;
        }

        /// <summary>
        /// Renders the data column. Used by the designer to render the column data in the design time.
        /// </summary>
        /// <remarks>
        /// This method should be used to show the rendered data in the Designer for the custom column. You do need this method otherwise.
        /// </remarks>
        /// <param name="container">The container.</param>
        /// <param name="data">The data.</param>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public override void RenderData(TableCell container, string data)
        {
            if (container == null)
            {
                throw new ArgumentNullException("container");
            }

            DropDownList ddl = new DropDownList();

            ddl.Enabled = false;
            if (DataSource != null)
                ddl.Items.Add("Databound");
            else
                ddl.Items.Add("Unbound");

            container.Controls.Add(ddl);
            ddl.ApplyStyle(ControlStyle);

            container.ApplyStyle(ItemStyle);
        }

        /// <summary>
        /// Builds the descriptor for this column.
        /// </summary>
        /// <returns>
        /// Returns the descriptor embedding all the properties of this column.
        /// </returns>
        [EditorBrowsable(EditorBrowsableState.Never)]
        protected override GridViewColumnScriptDescriptor BuildDescriptor()
        {
            GridViewColumnScriptDescriptor desc = base.BuildDescriptor();

            AddProperty(desc, "dataField", this.DataField);
            AddProperty(desc, "dataTextField", this.DropDownListDataText);
            AddProperty(desc, "dataTextFormatString", this.DropDownListDataTextFormatString);
            AddProperty(desc, "dataValueField", this.DropDownListDataValue);

            if (this.ReadOnly)
                desc.AddProperty("readOnly", this.ReadOnly);
            if (this.AllowDragAndDrop)
                desc.AddProperty("allowDragAndDrop", this.AllowDragAndDrop);

            AddProperty(desc, "dataSource", this.SerializeDataSource());

            return desc;
        }

        /// <summary>
        /// When implemented by a class, loads the server control's previously saved view state to the control.
        /// </summary>
        /// <param name="state">An <see cref="T:System.Object"/> that contains the saved view state values for the control.</param>
        protected override void LoadViewState(object state)
        {
            if (state != null)
            {
                object[] states = (object[])state;

                if (states[0] != null)
                    base.LoadViewState(states[0]);

                if (states[1] != null)
                    DataField = (string)states[1];

                if (states[2] != null)
                    DropDownListDataText = (string)states[2];

                if (states[3] != null)
                    DropDownListDataTextFormatString = (string)states[3];

                if (states[4] != null)
                    DropDownListDataValue = (string)states[4];

                if (states[5] != null)
                    ReadOnly = (bool)states[5];

                if (states[6] != null)
                    DataSource = (bool)states[6];
            }
        }

        /// <summary>
        /// When implemented by a class, saves the changes to a server control's view state to an <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// The <see cref="T:System.Object"/> that contains the view state changes.
        /// </returns>
        protected override object SaveViewState()
        {
            object[] states = new object[7];

            states[0] = base.SaveViewState();

            if (!string.IsNullOrEmpty(DataField))
                states[1] = DataField;

            if (!string.IsNullOrEmpty(DropDownListDataText))
                states[2] = DropDownListDataText;

            if (!string.IsNullOrEmpty(DropDownListDataTextFormatString))
                states[3] = DropDownListDataTextFormatString;

            if (!string.IsNullOrEmpty(DropDownListDataValue))
                states[4] = DropDownListDataValue;

            states[5] = ReadOnly;

            if (DataSource != null)
                states[6] = DataSource;

            return states;
        }

        private bool _selectCallback = false;
        private void OnDataSourceSelectCallback(IEnumerable target)
        {
            this.DataSource = target;
            _selectCallback = true;
        }

        private string SerializeDataSource()
        {
            if (_dataSource == null)
                return string.Empty;

            StringBuilder output = new StringBuilder();

            if (!(((_dataSource != null) && !(_dataSource is IListSource)) && (!(_dataSource is IEnumerable) && !(_dataSource is IDataSource))))
            {
                // try to get the data is the DataSource implements IDataSource.
                if (_dataSource is IDataSource)
                {
                    IDataSource dataSource = _dataSource as IDataSource;
                    dataSource.GetView("").Select(DataSourceSelectArguments.Empty, new DataSourceViewSelectCallback(OnDataSourceSelectCallback));

                    DateTime expectedEndTime = DateTime.Now.AddSeconds(5);
                    while (!_selectCallback && DateTime.Now < expectedEndTime) System.Threading.Thread.Sleep(0);

                    if (DateTime.Now > expectedEndTime)
                        throw new System.TimeoutException("DataSource select command timed out (5 seconds)!");
                }

                // now, if the DataSource implements ICollection, then we retrieve the data to output the JSON
                if (_dataSource is ICollection)
                {
                    ICollection dataSource = _dataSource as ICollection;
                    List<DropDownListItem> outputList = new List<DropDownListItem>(dataSource.Count);
                    string dataValueField = this.DropDownListDataValue;
                    string dataTextFormatString = this.DropDownListDataTextFormatString;
                    string dataTextField = this.DropDownListDataText;

                    IEnumerator enumerator = dataSource.GetEnumerator();
                    enumerator.Reset();
                    for (int i = 0; i < dataSource.Count; i++)
                    {
                        enumerator.MoveNext();

                        outputList.Add(new DropDownListItem(
                            DataBinder.GetPropertyValue(enumerator.Current, dataTextField, dataTextFormatString).ToString(),
                            DataBinder.GetPropertyValue(enumerator.Current, dataValueField).ToString()
                            ));
                    }

                    new JavaScriptSerializer().Serialize(outputList, output);
                }
            }
            else if (_dataSource != null && (_dataSource is string))
            {
                output.Append(_dataSource.ToString());
            }

            return output.ToString();
        }

        private void ValidateDataSource(object dataSource)
        {
            if (((dataSource != null) && !(dataSource is IListSource)) && (!(dataSource is IEnumerable) && !(dataSource is IDataSource)))
            {
                throw new InvalidOperationException("Invalid DataSource type!");
            }
        }
    }

    internal class DropDownListItem
    {
        private string value;
        private string text;

        public string Value
        {
            get { return this.value; }
            set { this.value = value; }
        }
        public string Text
        {
            get { return this.text; }
            set { this.text = value; }
        }

        public DropDownListItem(string text, string value)
        {
            this.Text = text;
            this.Value = value;
        }
    }
}
